package redis

import (
	"context"
	"corelib/log"
	"errors"
	"github.com/go-redis/redis/v8"
	"strings"
)

type RdsConfig struct {
	Name     string `json:"name"`      // 连接名称
	Addr     string `json:"addr"`      // redis地址
	Pwd      string `json:"pwd"`       // redis密码
	PoolSize int    `json:"pool_size"` // 连接池大小
	Prefix   string `json:"prefix"`    // 键名前缀
	DB       int    `json:"db_index"`
	//MaxLenApprox     int64  `json:"max_len_approx"`     // redis stream最大长度
	//NeedPersist      *bool  `json:"need_persist"`       // 是否需要持久化
	//CacheTTL         int64  `json:"cache_ttl"`          // 缓存过期时间,单位秒
	//PersistCd        int64  `json:"persist_cd"`         // 最短持久化周期,单位秒
	//PersistWorkerNum int64  `json:"persist_worker_num"` // 同时处理落地任务的协程数
	//KvPersistKey     string `json:"kv_persist_key"`     // kv落地队列的键
	//IsCluster        *bool  `json:"is_cluster"`         // 是否集群模式
	//LRUCacheCapacity int64  `json:"lru_cache_capacity"` // 内存lru缓存容量
	//LRUCacheTTL      *int64 `json:"lru_cache_ttl"`      // 内存lru缓存的过期时间,单位秒
	InitScript bool `json:"init_script"` //  初始化加载内部脚本
}

type RedisIns struct {
	ins       *redis.Client
	inf       *RdsConfig
	ctx       context.Context
	scriptMap map[string]*Script

	scripts *InnerScripts
}

func (r *RedisIns) initConf(c *RdsConfig) *RedisIns {
	if c.PoolSize > 100 {
		c.PoolSize = 100
	}
	if c.PoolSize < 10 {
		c.PoolSize = 10
	}
	r.inf = c
	return r
}

func (r *RedisIns) connect() error {
	r.ins = redis.NewClient(&redis.Options{
		Network:   "tcp",
		Addr:      r.inf.Addr,
		Dialer:    nil,
		OnConnect: nil,
		//Username:           "",
		Password: r.inf.Pwd,
		DB:       r.inf.DB,
		PoolSize: r.inf.PoolSize,
	})
	r.ctx = r.ins.Context()
	if e := r.ins.Ping(r.ctx).Err(); e != nil {
		return e
	}
	r.scriptMap = make(map[string]*Script)
	return nil
}

func (r *RedisIns) exec(cmd string, values ...interface{}) redis.Cmder {
	cmd = strings.ToUpper(cmd)
	args := []interface{}{cmd}
	if len(values) > 0 {
		args = append(args, values[0])
		if len(values) == 2 {
			args = appendArg(args, values[1])
		} else {
			args = append(args, values[1:]...)
		}
	}
	switch cmd {
	case "GEOPOS":
		c := redis.NewGeoPosCmd(r.ctx, args...)
		_ = r.ins.Process(r.ctx, c)
		return c
	default:
		return r.ins.Do(r.ctx, args...)
	}
}

func appendArg(dst []interface{}, arg interface{}) []interface{} {
	switch arg := arg.(type) {
	case []string:
		for _, s := range arg {
			dst = append(dst, s)
		}
		return dst
	case []interface{}:
		dst = append(dst, arg...)
		return dst
	case map[string]interface{}:
		for k, v := range arg {
			dst = append(dst, k, v)
		}
		return dst
	case map[string]string:
		for k, v := range arg {
			dst = append(dst, k, v)
		}
		return dst
	default:
		return append(dst, arg)
	}
}

func (r *RedisIns) exec2parse(cmd string, values ...interface{}) (res interface{}, err error) {
	return parseCmd(r.exec(cmd, values...))
}

func parseCmd(cmd redis.Cmder) (interface{}, error) {
	switch cmd.Args()[0].(string) {
	// common
	case "PING", "SELECT", "RENAME", "EVAL":
		return cmd.(*redis.Cmd).Text()
	case "EXPIRE", "EXPIREAT", "TTL", "DEL":
		return cmd.(*redis.Cmd).Int()
	case "EXISTS":
		return cmd.(*redis.Cmd).Bool()
	case "SCAN", "HSCAN", "SSCAN", "ZSCAN":
		return cmd.(*redis.Cmd).StringSlice()

	// k-v
	case "GET", "SET":
		return cmd.(*redis.Cmd).Text()
	case "INCRBY", "DECRBY", "APPEND":
		return cmd.(*redis.Cmd).Int()
	case "INCRBYFLOAT":
		return cmd.(*redis.Cmd).Float64()
	case "SETNX", "RENAMENX":
		return cmd.(*redis.Cmd).Bool()

	// hash
	case "HGET":
		return cmd.(*redis.Cmd).Text()
	case "HSET", "HDEL", "HINCRBY", "HLEN":
		return cmd.(*redis.Cmd).Int()
	case "HEXISTS", "HSETNX":
		return cmd.(*redis.Cmd).Bool()
	case "HMGET", "HGETALL", "HKEYS", "HVALS":
		return cmd.(*redis.Cmd).StringSlice()

	// list
	case "LPUSH", "LPUSHX", "LREM", "RPUSH", "RPUSHX", "LLEN", "LINSERT":
		return cmd.(*redis.Cmd).Int()
	case "RPOP", "LPOP", "LTRIM", "LINDEX", "RPOPLPUSH", "LMOVE": // RPOPLPUSH discard after Redis6.2.0 instead by LMOVE
		return cmd.(*redis.Cmd).Text()
	case "LRANGE":
		return cmd.(*redis.Cmd).StringSlice()

	// zset
	case "ZADD", "ZREM", "ZCARD", "ZCOUNT", "ZINCRBY", "ZLEXCOUNT", "ZRANK", "ZREVRANK":
		return cmd.(*redis.Cmd).Int()
	case "ZPOPMAX", "ZPOPMIN", "ZRANGE", "ZRANGEBYSCORE", "ZREVRANGE", "ZREVRANGEBYSCORE":
		return cmd.(*redis.Cmd).StringSlice()

	// set
	case "SADD", "SCARD", "SDIFFSTORE", "SINTERSTORE", "SISMEMBER", "SMOVE", "SREM", "SUNIONSTORE":
		return cmd.(*redis.Cmd).Int()
	case "SMEMBERS", "SDIFF", "SINTER", "SMISMEMBER", "SPOP", "SRANDMEMBER", "SUNION":
		return cmd.(*redis.Cmd).StringSlice()
	// pub unsub
	case "PUBLISH":
		return cmd.(*redis.Cmd).Int()
	case "PUNSUBSCRIBE", "UNSUBSCRIBE":
		return cmd.(*redis.Cmd).Slice()
	// GEO
	case "GEOADD":
		return cmd.(*redis.Cmd).Int()
	case "GEOPOS":
		return cmd.(*redis.GeoPosCmd).Result()
	case "GEODIST":
		return cmd.(*redis.Cmd).Float64()
	case "GEOHASH":
		return cmd.(*redis.Cmd).StringSlice()

	default:
		return nil, errors.New("valid redis cmd")
	}
}

func (r *RedisIns) loadScript(luaScript string) (*Script, error) {
	rdScript := redis.NewScript(luaScript)

	if e := rdScript.Load(r.ctx, r.ins).Err(); e != nil {
		log.Printf("error in loadScript %v,lua:%v", e, luaScript)
		return nil, e
	}
	sc := &Script{
		rdsIns: r,
		script: rdScript,
	}
	r.scriptMap[rdScript.Hash()] = sc
	return sc, nil
}
